
#include <gfx/ieffect.h>
#include <gfx/material.h>
#include <scene/clipper.h>
#include <scene/entitynode.h>
#include <scene/ieffectcamera.h>
#include <scene/geometry.h>
#include <scene/lodnode.h>
#include <scene/portal.h>
#include <scene/renderbucket.h>
#include <scene/scene.h>
#include <scene/sector.h>
#include <utils/nodescanner.h>
#include <utils/utils.h>
#include <string.h>


ceNodeScanner::ceNodeScanner(iDevice* device, ceRenderBucket* bucket, ceRenderEnvironment* env)
  : _device(device)
  , _bucket(bucket)
  , _env(env)
  , _update (true)
  , _collectEffectCameras(true)
  , _collectLights (true)
  , _collectShadowCaster (CSC_All)
{
}

void ceNodeScanner::SetUpdate(bool update)
{
  _update = update;
}

void ceNodeScanner::SetCollectEffectCameras(bool collect)
{
  _collectEffectCameras = collect;
}

void ceNodeScanner::SetCollectLights(bool collect)
{
  _collectLights = collect;
}

void ceNodeScanner::SetCollectShadowCasters(bool caster, bool nonCaster)
{
  _collectShadowCaster = 0;
  if (caster)
    {
      _collectShadowCaster |= CSC_Caster;
    }
  if (nonCaster)
    {
      _collectShadowCaster |= CSC_NonCaster;
    }
}

void ceNodeScanner::SetDevice(iDevice *device)
{
  _device = device;
}

void ceNodeScanner::SetRenderBucket(ceRenderBucket *bucket)
{
  _bucket = bucket;
}

void ceNodeScanner::SetRenderEnvironment(ceRenderEnvironment *env)
{
  _env = env;
}

void ceNodeScanner::Scan(ceEntityNode *entity, iClipper *)
{

  ceGeometry* geometry = entity->GetGeometry();
  if (geometry)
    {
      if ( entity->CastsShadow() && CE_FLAG(_collectShadowCaster, CSC_Caster) ||
          !entity->CastsShadow() && CE_FLAG(_collectShadowCaster, CSC_NonCaster))
        {
          ceMaterial* material = geometry->GetMaterial();
					if (material->GetEffect ())
						{
              float dist = (entity->GetGlobalTranslation() - _env->CameraLocationGlobal).Length();
							_bucket->Add(material->GetEffect()->Eval (dist, geometry->GetBoundingBox().GetMediumRadius()),
													 material->GetTextures(),
													 material->GetMaterialSpec(),
													 material->GetSharedShaderBinding(),
                           entity->GetGlobalMatrix(),
													 geometry->GetMesh(),
													 entity);
						}
        }
    }
  ceLightObject* light = entity->GetLight();
  if (light && _collectLights)
    {
      _bucket->Add(light);
    }

  if (_collectEffectCameras)
    {
      for (unsigned i=0, j=entity->GetNumberOfEffectCameras(); i<j; i++)
        {
          iEffectCamera* ec = entity->GetEffectCamera(i);
          if (ec->Update(*_env, _device))
            {
              _bucket->Add(ec);
            }
        }
    }
}

void ceNodeScanner::Scan(ceLODNode *lod, iClipper *clipper)
{
  ceNode* n = lod->GetNode();
  if (n)
    {
      Scan(n, clipper);
    }
}

void ceNodeScanner::Scan(ceNode *node, iClipper *clipper)
{
  const ceNode* cn = node;
  if (!node->IsClip())
    {
      // node and all the subnodes will not get clipped
      clipper = 0;
    }
  if (clipper)
    {
      ceClipResult cr = clipper->Test(cn->GetBoundingBox());
      switch (cr)
        {
        case CR_In:
          clipper = 0;
          break;
        case CR_Out:
          return;
        default:
          break;
        }
    }
  if (_update)
    {
      node->Update(*_env, _device);
    }
  switch (node->GetNodeType())
    {
    case NT_EntityNode:
      Scan (static_cast<ceEntityNode*>(node), clipper);
      break;
    case NT_LODNode:
      Scan (static_cast<ceLODNode*>(node), clipper);
      break;
    }

  // now check the child nodes.
  for (size_t i=0, ni=node->GetNumberOfChildren(); i<ni; ++i)
    {
      Scan (node->GetChild(i), clipper);
    }
}

bool ceNodeScanner::TestPortal (cePortal* portal, iClipper* clipper)
{
	if (!portal->IsOpen())
		{
			return false;
		}

	unsigned numberVertices = portal->GetNumberOfVertices ();
	if (!numberVertices)
		{
			return true;
		}

	ceClipResult res = clipper->Test (portal->GetVertices (), numberVertices);

	// everything that is not completly outside is within
	return res != CR_Out;
}

void ceNodeScanner::Scan(ceSector *sector, iClipper* clipper)
{
	if (sector->GetLastScannedFrame () == _env->Frame)
		{
			// this node is already scanned
			return;
		}
	sector->SetLastScannedFrame(_env->Frame);

  NodeListIterator it;
  NodeList& nodes = sector->GetNodes();
  for (it = nodes.begin (); it != nodes.end (); ++it)
    {
      ceNode* n = *it;
      Scan (n, clipper);
    }

	for (unsigned i=0, j=sector->GetNumberOfPortals (); i<j; i++)
		{
			cePortal* portal = sector->GetPortal (i);
			if (!TestPortal (portal, clipper))
				{
					continue;
				}
			ceSector* other = portal->GetSector (sector);
			Scan (other, clipper);
		}
}

void ceNodeScanner::Scan(ceNode *node, const ceCamera &camera, const ceProjector &projector, unsigned frame)
{
  memset(_env, 0, sizeof(ceRenderEnvironment));
  _env->CameraLocationGlobal = camera.GetEye();
  _env->Frame = frame;
	_env->ProjectionMatrix = projector.GetMatrix ();
	_env->ViewMatrix = camera.GetMatrix ();

  cePlaneClipper clipper;
  clipper.Create(camera, projector);
  _bucket->Clear ();

  Scan (node, &clipper);
}

void ceNodeScanner::Scan(ceScene* scene, const ceCamera &camera, const ceProjector &projector, unsigned frame)
{
  memset(_env, 0, sizeof(ceRenderEnvironment));
  _env->CameraLocationGlobal = camera.GetEye();
	_env->ProjectionMatrix = projector.GetMatrix ();
	_env->ViewMatrix = camera.GetMatrix ();
  _env->Frame = frame;

  cePlaneClipper clipper;
  clipper.Create(camera, projector);
  _bucket->Clear ();

	if (!scene)
		{
			return;
		}
	ceSector* sector = camera.GetSector ();
	if (sector)
		{
			Scan(sector, &clipper);
		}
	else
		{
			ceSectorList& sectors = scene->GetSectors();
			ceSectorListIterator it;
			for (it = sectors.begin (); it != sectors.end (); ++it)
				{
					sector = *it;
					Scan(sector, &clipper);
				}
		}
}
